package com.asen.libcommon.base.adapter

import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Parcelable
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.CallSuper
import androidx.collection.ArraySet
import androidx.collection.LongSparseArray
import androidx.core.util.Preconditions
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import androidx.viewpager2.adapter.StatefulAdapter
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList

/**
 * @author : asenLiang
 * @date   : 2021/5/26
 * @e-mail : liangAisiSen@163.com
 * @desc   : 重构 ViewPager2 适配器 FragmentStateAdapter ，在原来的基础上面添加 获取Fragment ，解决官方无获取 Fragment 方法
 */
abstract class FragmentStateAdapter(
    val mFragmentManager: FragmentManager,
    val mLifecycle: Lifecycle
) : RecyclerView.Adapter<FragmentStateAdapter.FragmentViewHolder>(), StatefulAdapter {

    private val mFragments = LongSparseArray<Fragment?>()
    private val mSavedStates = LongSparseArray<Fragment.SavedState?>()
    private val mItemIdToViewHolder = LongSparseArray<Int>()
    private var mFragmentMaxLifecycleEnforcer: FragmentMaxLifecycleEnforcer? = null
    var mFragmentEventDispatcher = FragmentEventDispatcher()
    var mIsInGracePeriod = false
    private var mHasStaleFragments = false

    constructor(fragmentActivity: FragmentActivity) : this(fragmentActivity.supportFragmentManager, fragmentActivity.lifecycle)

    constructor(fragment: Fragment) : this(fragment.childFragmentManager, fragment.lifecycle)

    @SuppressLint("RestrictedApi")
    @CallSuper
    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        Preconditions.checkArgument(mFragmentMaxLifecycleEnforcer == null)
        mFragmentMaxLifecycleEnforcer = FragmentMaxLifecycleEnforcer()
        mFragmentMaxLifecycleEnforcer!!.register(recyclerView)
    }

    @CallSuper
    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        mFragmentMaxLifecycleEnforcer!!.unregister(recyclerView)
        mFragmentMaxLifecycleEnforcer = null
    }

    abstract fun createFragment(position: Int): Fragment
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FragmentViewHolder {
        return FragmentViewHolder.create(parent)
    }

    override fun onBindViewHolder(holder: FragmentViewHolder, position: Int) {
        val itemId = holder.itemId
        val viewHolderId = holder.container.id
        val boundItemId = itemForViewHolder(viewHolderId)
        if (boundItemId != null && boundItemId != itemId) {
            removeFragment(boundItemId)
            mItemIdToViewHolder.remove(boundItemId)
        }
        mItemIdToViewHolder.put(itemId, viewHolderId)
        ensureFragment(position)
        val container = holder.container
        if (ViewCompat.isAttachedToWindow(container)) {
            check(container.parent == null) { "Design assumption violated." }
            container.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
                override fun onLayoutChange(
                    v: View, left: Int, top: Int, right: Int, bottom: Int,
                    oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int
                ) {
                    if (container.parent != null) {
                        container.removeOnLayoutChangeListener(this)
                        placeFragmentInViewHolder(holder)
                    }
                }
            })
        }
        gcFragments()
    }

    fun gcFragments() {
        if (!mHasStaleFragments || shouldDelayFragmentTransactions()) return

        val toRemove: MutableSet<Long> = ArraySet()

        for (ix in 0 until mFragments.size()) {
            val itemId = mFragments.keyAt(ix)
            if (!containsItem(itemId)) {
                toRemove.add(itemId)
                mItemIdToViewHolder.remove(itemId)
            }
        }

        if (!mIsInGracePeriod) {
            mHasStaleFragments = false
            for (ix in 0 until mFragments.size()) {
                val itemId = mFragments.keyAt(ix)
                if (!isFragmentViewBound(itemId)) {
                    toRemove.add(itemId)
                }
            }
        }

        for (itemId in toRemove) {
            removeFragment(itemId)
        }
    }

    private fun isFragmentViewBound(itemId: Long): Boolean {
        if (mItemIdToViewHolder.containsKey(itemId)) return true

        val fragment = mFragments[itemId] ?: return false
        val view = fragment.view ?: return false
        return view.parent != null
    }

    private fun itemForViewHolder(viewHolderId: Int): Long? {
        var boundItemId: Long? = null
        for (ix in 0 until mItemIdToViewHolder.size()) {
            if (mItemIdToViewHolder.valueAt(ix) == viewHolderId) {
                check(boundItemId == null) {
                    ("Design assumption violated: " + "a ViewHolder can only be bound to one item at a time.")
                }
                boundItemId = mItemIdToViewHolder.keyAt(ix)
            }
        }
        return boundItemId
    }

    private fun ensureFragment(position: Int) {
        val itemId = getItemId(position)
        if (!mFragments.containsKey(itemId)) {
            val newFragment = createFragment(position)
            newFragment.setInitialSavedState(mSavedStates[itemId])
            mFragments.put(itemId, newFragment)
        }
    }

    fun <T:Fragment> getFragment(position: Int): T? {
        val itemId = getItemId(position)
        return if (mFragments.containsKey(itemId)) mFragments.get(itemId) as T else null
    }

    override fun onViewAttachedToWindow(holder: FragmentViewHolder) {
        placeFragmentInViewHolder(holder)
        gcFragments()
    }

    fun placeFragmentInViewHolder(holder: FragmentViewHolder) {
        val fragment = mFragments[holder.itemId] ?: throw IllegalStateException("Design assumption violated.")
        val container = holder.container
        val view = fragment.view
        check(!(!fragment.isAdded && view != null)) { "Design assumption violated." }
        if (fragment.isAdded && view == null) {
            scheduleViewAttach(fragment, container)
            return
        }
        if (fragment.isAdded && view!!.parent != null) {
            if (view.parent !== container) addViewToContainer(view, container)
            return
        }

        if (fragment.isAdded) {
            addViewToContainer(view!!, container)
            return
        }

        if (!shouldDelayFragmentTransactions()) {
            scheduleViewAttach(fragment, container)
            val onPost =
                mFragmentEventDispatcher.dispatchPreAdded(fragment)
            try {
                fragment.setMenuVisibility(false) // appropriate for maxLifecycle == STARTED
                mFragmentManager.beginTransaction()
                    .add(fragment, "f" + holder.itemId)
                    .setMaxLifecycle(fragment, Lifecycle.State.STARTED)
                    .commitNow()
                mFragmentMaxLifecycleEnforcer!!.updateFragmentMaxLifecycle(false)
            } finally {
                mFragmentEventDispatcher.dispatchPostEvents(onPost)
            }
        } else {
            if (mFragmentManager.isDestroyed) return

            mLifecycle.addObserver(object : LifecycleEventObserver {
                override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                    if (shouldDelayFragmentTransactions()) return

                    source.lifecycle.removeObserver(this)
                    if (ViewCompat.isAttachedToWindow(holder.container)) {
                        placeFragmentInViewHolder(holder)
                    }
                }
            })
        }
    }

    private fun scheduleViewAttach(fragment: Fragment, container: FrameLayout) {
        mFragmentManager.registerFragmentLifecycleCallbacks(
            object : FragmentManager.FragmentLifecycleCallbacks() {
                override fun onFragmentViewCreated(fm: FragmentManager, f: Fragment, v: View, savedInstanceState: Bundle?) {
                    if (f === fragment) {
                        fm.unregisterFragmentLifecycleCallbacks(this)
                        addViewToContainer(v, container)
                    }
                }
            }, false
        )
    }

    fun addViewToContainer(v: View, container: FrameLayout) {
        check(container.childCount <= 1) { "Design assumption violated." }
        if (v.parent === container) return

        if (container.childCount > 0) container.removeAllViews()

        if (v.parent != null) (v.parent as ViewGroup).removeView(v)

        container.addView(v)
    }

    override fun onViewRecycled(holder: FragmentViewHolder) {
        val viewHolderId = holder.container.id
        val boundItemId = itemForViewHolder(viewHolderId)
        if (boundItemId != null) {
            removeFragment(boundItemId)
            mItemIdToViewHolder.remove(boundItemId)
        }
    }

    override fun onFailedToRecycleView(holder: FragmentViewHolder): Boolean { return true }

    private fun removeFragment(itemId: Long) {
        val fragment = mFragments[itemId] ?: return
        if (fragment.view != null) {
            val viewParent = fragment.view!!.parent
            if (viewParent != null) {
                (viewParent as FrameLayout).removeAllViews()
            }
        }

        if (!containsItem(itemId)) {
            mSavedStates.remove(itemId)
        }

        if (!fragment.isAdded) {
            mFragments.remove(itemId)
            return
        }

        if (shouldDelayFragmentTransactions()) {
            mHasStaleFragments = true
            return
        }
        if (fragment.isAdded && containsItem(itemId)) {
            mSavedStates.put(itemId, mFragmentManager.saveFragmentInstanceState(fragment))
        }

        val onPost = mFragmentEventDispatcher.dispatchPreRemoved(fragment)
        try {
            mFragmentManager.beginTransaction().remove(fragment).commitNow()
            mFragments.remove(itemId)
        } finally {
            mFragmentEventDispatcher.dispatchPostEvents(onPost)
        }
    }

    fun shouldDelayFragmentTransactions(): Boolean { return mFragmentManager.isStateSaved }

    override fun getItemId(position: Int): Long { return position.toLong() }

    fun containsItem(itemId: Long): Boolean { return itemId >= 0 && itemId < itemCount }

    override fun setHasStableIds(hasStableIds: Boolean) {
        throw UnsupportedOperationException("Stable Ids are required for the adapter to function properly, and the adapter takes care of setting the flag.")
    }

    override fun saveState(): Parcelable {
        val savedState = Bundle(mFragments.size() + mSavedStates.size())
        for (ix in 0 until mFragments.size()) {
            val itemId = mFragments.keyAt(ix)
            val fragment = mFragments[itemId]
            if (fragment != null && fragment.isAdded) {
                val key = createKey(KEY_PREFIX_FRAGMENT, itemId)
                mFragmentManager.putFragment(savedState, key, fragment)
            }
        }
        for (ix in 0 until mSavedStates.size()) {
            val itemId = mSavedStates.keyAt(ix)
            if (containsItem(itemId)) {
                val key = createKey(KEY_PREFIX_STATE, itemId)
                savedState.putParcelable(key, mSavedStates[itemId])
            }
        }
        return savedState
    }

    override fun restoreState(savedState: Parcelable) {
        check(!(!mSavedStates.isEmpty || !mFragments.isEmpty)) { "Expected the adapter to be 'fresh' while restoring state." }
        val bundle = savedState as Bundle
        if (bundle.classLoader == null) bundle.classLoader = javaClass.classLoader

        for (key in bundle.keySet()) {
            if (isValidKey(key, KEY_PREFIX_FRAGMENT)) {
                val itemId = parseIdFromKey(key, KEY_PREFIX_FRAGMENT)
                val fragment = mFragmentManager.getFragment(bundle, key)
                mFragments.put(itemId, fragment)
                continue
            }
            if (isValidKey(key, KEY_PREFIX_STATE)) {
                val itemId = parseIdFromKey(key, KEY_PREFIX_STATE)
                val state: Fragment.SavedState? = bundle.getParcelable(key)
                if (containsItem(itemId)) mSavedStates.put(itemId, state)
                continue
            }
            throw IllegalArgumentException("Unexpected key in savedState: $key")
        }
        if (!mFragments.isEmpty) {
            mHasStaleFragments = true
            mIsInGracePeriod = true
            gcFragments()
            scheduleGracePeriodEnd()
        }
    }

    private fun scheduleGracePeriodEnd() {
        val handler = Handler(Looper.getMainLooper())
        val runnable = Runnable {
            mIsInGracePeriod = false
            gcFragments() }
        mLifecycle.addObserver(object : LifecycleEventObserver {
            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    handler.removeCallbacks(runnable)
                    source.lifecycle.removeObserver(this)
                }
            }
        })
        handler.postDelayed(runnable, GRACE_WINDOW_TIME_MS)
    }

    internal inner class FragmentMaxLifecycleEnforcer {
        private var mPageChangeCallback: OnPageChangeCallback? = null
        private var mDataObserver: AdapterDataObserver? = null
        private var mLifecycleObserver: LifecycleEventObserver? = null
        private var mViewPager: ViewPager2? = null
        private var mPrimaryItemId = RecyclerView.NO_ID
        fun register(recyclerView: RecyclerView) {
            mViewPager = inferViewPager(recyclerView)
            mPageChangeCallback = object : OnPageChangeCallback() {
                override fun onPageScrollStateChanged(state: Int) {
                    updateFragmentMaxLifecycle(false)
                }

                override fun onPageSelected(position: Int) {
                    updateFragmentMaxLifecycle(false)
                }
            }
            mViewPager!!.registerOnPageChangeCallback(mPageChangeCallback as OnPageChangeCallback)

            mDataObserver = object : DataSetChangeObserver() {
                override fun onChanged() {
                    updateFragmentMaxLifecycle(true)
                }
            }
            registerAdapterDataObserver(mDataObserver as DataSetChangeObserver)
            mLifecycleObserver = LifecycleEventObserver { source, event -> updateFragmentMaxLifecycle(false) }
            mLifecycle.addObserver(mLifecycleObserver!!)
        }

        fun unregister(recyclerView: RecyclerView) {
            val viewPager = inferViewPager(recyclerView)
            viewPager.unregisterOnPageChangeCallback(mPageChangeCallback!!)
            unregisterAdapterDataObserver(mDataObserver!!)
            mLifecycle.removeObserver(mLifecycleObserver!!)
            mViewPager = null
        }

        fun updateFragmentMaxLifecycle(dataSetChanged: Boolean) {
            if (shouldDelayFragmentTransactions()) return

            if (mViewPager!!.scrollState != ViewPager2.SCROLL_STATE_IDLE) return

            if (mFragments.isEmpty || itemCount == 0) return

            val currentItem = mViewPager!!.currentItem
            if (currentItem >= itemCount) return

            val currentItemId = getItemId(currentItem)
            if (currentItemId == mPrimaryItemId && !dataSetChanged) return

            val currentItemFragment = mFragments[currentItemId]
            if (currentItemFragment == null || !currentItemFragment.isAdded) return

            mPrimaryItemId = currentItemId
            val transaction = mFragmentManager.beginTransaction()
            var toResume: Fragment? = null
            val onPost: MutableList<List<FragmentTransactionCallback.OnPostEventListener>?> = ArrayList()
            for (ix in 0 until mFragments.size()) {
                val itemId = mFragments.keyAt(ix)
                val fragment = mFragments.valueAt(ix)
                if (!fragment!!.isAdded) continue

                if (itemId != mPrimaryItemId) {
                    transaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED)
                    onPost.add(mFragmentEventDispatcher.dispatchMaxLifecyclePreUpdated(fragment, Lifecycle.State.STARTED))
                } else {
                    toResume = fragment
                }
                fragment.setMenuVisibility(itemId == mPrimaryItemId)
            }
            if (toResume != null) {
                transaction.setMaxLifecycle(toResume, Lifecycle.State.RESUMED)
                onPost.add(mFragmentEventDispatcher.dispatchMaxLifecyclePreUpdated(toResume, Lifecycle.State.RESUMED))
            }
            if (!transaction.isEmpty) {
                transaction.commitNow()
                Collections.reverse(onPost) // to assure 'nesting' of events
                for (event in onPost) {
                    mFragmentEventDispatcher.dispatchPostEvents(event)
                }
            }
        }

        private fun inferViewPager(recyclerView: RecyclerView): ViewPager2 {
            val parent = recyclerView.parent
            if (parent is ViewPager2) {
                return parent
            }
            throw IllegalStateException("Expected ViewPager2 instance. Got: $parent")
        }
    }

    private abstract class DataSetChangeObserver : AdapterDataObserver() {
        abstract override fun onChanged()
        override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
            onChanged()
        }

        override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
            onChanged()
        }

        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
            onChanged()
        }

        override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
            onChanged()
        }

        override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
            onChanged()
        }
    }

    class FragmentEventDispatcher {
        private val mCallbacks: MutableList<FragmentTransactionCallback> = CopyOnWriteArrayList()

        fun registerCallback(callback: FragmentTransactionCallback) {
            mCallbacks.add(callback)
        }

        fun unregisterCallback(callback: FragmentTransactionCallback?) {
            mCallbacks.remove(callback)
        }

        fun dispatchMaxLifecyclePreUpdated(fragment: Fragment, maxState: Lifecycle.State): List<FragmentTransactionCallback.OnPostEventListener> {
            val result: MutableList<FragmentTransactionCallback.OnPostEventListener> = ArrayList()
            for (callback in mCallbacks) {
                result.add(callback.onFragmentMaxLifecyclePreUpdated(fragment, maxState))
            }
            return result
        }

        fun dispatchPostEvents(entries: List<FragmentTransactionCallback.OnPostEventListener>?) {
            for (entry in entries!!) {
                entry.onPost()
            }
        }

        fun dispatchPreAdded(fragment: Fragment): List<FragmentTransactionCallback.OnPostEventListener> {
            val result: MutableList<FragmentTransactionCallback.OnPostEventListener> = ArrayList()
            for (callback in mCallbacks) {
                result.add(callback.onFragmentPreAdded(fragment))
            }
            return result
        }

        fun dispatchPreRemoved(fragment: Fragment): List<FragmentTransactionCallback.OnPostEventListener> {
            val result: MutableList<FragmentTransactionCallback.OnPostEventListener> = ArrayList()
            for (callback in mCallbacks) {
                result.add(callback.onFragmentPreRemoved(fragment))
            }
            return result
        }
    }

    abstract class FragmentTransactionCallback {
        fun onFragmentPreAdded(fragment: Fragment): OnPostEventListener {
            return NO_OP
        }

        fun onFragmentPreRemoved(fragment: Fragment): OnPostEventListener {
            return NO_OP
        }

        fun onFragmentMaxLifecyclePreUpdated(fragment: Fragment, maxLifecycleState: Lifecycle.State): OnPostEventListener {
            return NO_OP
        }

        interface OnPostEventListener {
            fun onPost()
        }

        companion object {
            private val NO_OP: OnPostEventListener =
                object : OnPostEventListener {
                    override fun onPost() {}
                }
        }
    }

    fun registerFragmentTransactionCallback(callback: FragmentTransactionCallback) {
        mFragmentEventDispatcher.registerCallback(callback)
    }

    fun unregisterFragmentTransactionCallback(callback: FragmentTransactionCallback) {
        mFragmentEventDispatcher.unregisterCallback(callback)
    }

    companion object {
        // State saving config
        private const val KEY_PREFIX_FRAGMENT = "f#"
        private const val KEY_PREFIX_STATE = "s#"
        private const val GRACE_WINDOW_TIME_MS: Long = 10000 // 10 seconds
        private fun createKey(prefix: String, id: Long): String {
            return prefix + id
        }

        private fun isValidKey(key: String, prefix: String): Boolean {
            return key.startsWith(prefix) && key.length > prefix.length
        }

        private fun parseIdFromKey(key: String, prefix: String): Long {
            return key.substring(prefix.length).toLong()
        }
    }

    init {
        super.setHasStableIds(true)
    }

    class FragmentViewHolder private constructor(container: FrameLayout) : RecyclerView.ViewHolder(container) {
        val container: FrameLayout
            get() = itemView as FrameLayout

        companion object {
            fun create(parent: ViewGroup): FragmentViewHolder {
                val container = FrameLayout(parent.context)
                container.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
                container.id = ViewCompat.generateViewId()
                container.isSaveEnabled = false
                return FragmentViewHolder(container)
            }
        }
    }
}

